package com.hspedu.spring.ioc;

import com.hspedu.spring.annotation.Autowire;
import com.hspedu.spring.annotation.Component;
import com.hspedu.spring.annotation.Scope;
import com.hspedu.spring.processor.BeanPostProcessor;
import com.hspedu.spring.processor.InitializingBean;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author xianYun
 * @version 1.0
 **/
public class HspApplicationContext {
    private Class configClass;//配置类对象的 Class 属性

    //beanDefinitionMap 存放 id 值，和 BeanDefinition 对象
    private ConcurrentHashMap<String, BeanDefinition>
            beanDefinitionMap = new ConcurrentHashMap<>();
    //创建单例池，存放单例对象
    private ConcurrentHashMap<String, Object>
            singletonObjects = new ConcurrentHashMap<>();
    private List<BeanPostProcessor> beanPostProcessors
             = new ArrayList<>();

    public HspApplicationContext(Class configClass) {
        //扫描指定的包，完成ioc对象的封装
        beanDefinitionByScan(configClass);

        //通过 beanDefinitionMap 初始化 singletonObjects （单例池）
        Enumeration<String> keys = beanDefinitionMap.keys();//相当于取到了所有 bean 的名字
        while (keys.hasMoreElements()) {
            String beanName = keys.nextElement();
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                Object bean = createBean(beanName,beanDefinition);
                singletonObjects.put(beanName, bean);
            }
        }

    }

    /**
     * 通过反射返回对象
     *
     * @param beanDefinition
     * @return
     */
    private Object createBean(String beanName,BeanDefinition beanDefinition) {
        Class clazz = beanDefinition.getClazz();
        try {
            //使用 clazz（Class对象）通过构造器创建对象实例
            Object instance = clazz.getConstructor().newInstance();
            //这里要完成自动装配的相关逻辑
            //1. 遍历当前对象的所有字段
            for (Field declareField : clazz.getDeclaredFields()) {
                //2. 判断该字段是否有 Autowire 注解修饰
                if (declareField.isAnnotationPresent(Autowire.class)) {
                    String name = declareField.getName();//3. 获得字段的名字
                    //4. 通过 getBean() 获得需要注入的对象
                    Object bean = getBean(name);
                    //5. 进行注入，instance 是注入的主体，bean 是需要注入的对象
                    declareField.setAccessible(true);//进行反射爆破，以便于访问私有属性
                    declareField.set(instance, bean);
                }
            }


            System.out.println("!==ioc容易已创建对象实例==>" + instance);
            //在初始化方法之前调用 后置处理器的 before 方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
                Object current = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
                if (current != null){
                    instance = current;
                }
            }

            //这里体现了接口编程的实际应用，通过判断类是否实现了某个接口，
            // 判断类是否需要走某个业务
            if (instance instanceof InitializingBean){
                InitializingBean instance1 = (InitializingBean) instance;
                instance1.afterPropertiesSet();//这里相当于调用初始化方法
            }

            //在初始化方法后调用 后置处理器的 after 方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
                Object current = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
                if (current != null){
                    instance = current;
                }
            }
            return instance;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 根据 id 获取对应的 bean 对象
     *
     * @param name
     * @return
     */
    public Object getBean(String name) {
        BeanDefinition beanDefinition = beanDefinitionMap.get(name);
        if (beanDefinitionMap.containsKey(name)) {
            //判断注解 Scope 的值，以确定根据哪种方式返回 bean 对象
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                //说明是单例，那就从单例池获取
                return singletonObjects.get(name);
            } else {//如果不是单例，则调用 createBean() 通过反射创建新对象
                return createBean(name,beanDefinition);
            }
        } else {
            throw new NullPointerException("找不到对应的 bean 对象");
        }

    }

    /**
     * 该方法完成对 bean 对象的扫描，并将其封装到 beanDefinitionMap
     *
     * @param configClass
     */
    private void beanDefinitionByScan(Class configClass) {
        //获取配置类的 Class 对象，以获取配置类中的信息（配置类中的信息都是在注解中的）
        this.configClass = configClass;
        //获取配置类中的注解
        Component declaredAnnotation =
                (Component) configClass.getDeclaredAnnotation(Component.class);
        String path = declaredAnnotation.value();//获取注解中配置的类路径
        System.out.println("获取到注解中配置的类路径：" + path);
        //将类路径中的 "." 换成 "/" 形成文件路径
        path = path.replace(".", "/");
        //从configClass类加载路径上去找资源，
        URL resource = configClass.getClassLoader().getResource(path);
        System.out.println("找到了需要扫描的包：" + resource);
        File file = new File(resource.getFile());//创建资源文件对象
        //通过 file 对象判断该 资源文件对象是否为目录
        if (file.isDirectory()) {
            File[] files = file.listFiles();//如果是目录，则获取目录下的文件名
            for (File fileItem : files) {//对获得的文件名进行遍历
                String fileItemAbsolutePath = fileItem.getAbsolutePath();//获取配置类注解中的配置的 对象名
                //进行过滤只处理 Class 文件
                if (fileItemAbsolutePath.endsWith(".class")) {
                    // D:\CodeStore\JavaCode\hspedu_spring\Spring\out\production\Spring\com\hspedu\spring\component\UserService.class
                    String className = fileItemAbsolutePath.substring(fileItemAbsolutePath.lastIndexOf("\\") + 1,
                            fileItemAbsolutePath.lastIndexOf(".class"));//获取类名
                    //重新组合出全包名
                    String classFullName = path.replace("/", ".") + "." + className;
                    System.out.println("===================================");
                    System.out.println("从注解中获取到的全包名是：" + classFullName);

                    //判断该类是不是需要注入 ioc
                    try {
                        //这里的 Class 对象已经不是配置类的了，而是配置类注解上写的目标 bean 的 Class 对象
                        Class<?> clazz = configClass.getClassLoader().loadClass(classFullName);//获得配置类注解中指定类的 Class 对象
                        //isAnnotationPresent() 判断指定元素上有没有指定类型的注解
                        if (clazz.isAnnotationPresent(Component.class)) {
                            /*
                            这里对 Spring 容器进行了简化，将 BeanPostProcessor 放在了 beanPostProcessors 属性中
                            原生的 Spring 是把 后置处理器放在到单例池中的
                             */
                            //判断 Class 类型是否实现了某一个接口，不能用 instanceof，应该用 父类型.class.isAssignableFrom(需要判断的Class类型)
                            if (BeanPostProcessor.class.isAssignableFrom(clazz)){
                                BeanPostProcessor beanPostProcessor =
                                        (BeanPostProcessor) clazz.newInstance();
                                beanPostProcessors.add(beanPostProcessor);
                                continue;
                            }

                            //创建 ioc 容器的默认 id
                            Component component = clazz.getDeclaredAnnotation(Component.class);//获取 clazz 对象上的 Component 注解，获取注解中配置的 id 值
                            String id = component.value();
                            if ("".equals(id)) {
                                //Spring 提供的将首字母变成小写的方法 StringUtils.uncapitalize()
                                id = StringUtils.uncapitalize(className);
                            }


                            System.out.println("是 bean 对象" + clazz + "类名：" + className);
                            //将 bean 对象的信息封装进 BeanDefinition 中，---> 放进 beanDefinitionMap 属性中
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setClazz(clazz);//将目标 bean 的 Class 对象放进 BeanDefinition 中
                            //beanDefinitionMap 对象中的 scope 属性赋值
                            //获取 scope 值
                            if (clazz.isAnnotationPresent(Scope.class)) {
                                Scope scope = clazz.getDeclaredAnnotation(Scope.class);
                                String scopeValue = scope.value();
                                //防止给 Scope 空值
                                if ("".equals(scope.value())) {
                                    scopeValue = "singleton";
                                }
                                beanDefinition.setScope(scopeValue);
                            } else {
                                //如果目标 bean 没有 Scope 注解，则默认单例模式
                                beanDefinition.setScope("singleton");
                            }
                            //将 beanDefinition 放进 beanDefinitionMap
                            beanDefinitionMap.put(id, beanDefinition);

                        } else {
                            System.out.println("你不是 bean 对象" + clazz + "类名：" + className);
                        }
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
}
